﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Legend.Collections;
using System.Diagnostics;
using FakeItEasy; using FakeItEasy.ExtensionSyntax.Full;
using System.Collections;
using Legend.Threading;
using System.Threading;

namespace Legend.Core.Tests.Collections.Collection
{
    [TestFixture]
    public class Contains
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_predicate_is_null()
        {
            "abc".Contains((Func<char, bool>)null);
        }

        [Test]
        public void should_return_true_when_collection_contains_a_match()
        {
            "abc".Contains(c => c.Equals('b'));
        }

        [Test]
        public void should_return_false_when_collection_does_not_contain_a_match()
        {
            "abc".Contains(c => c.Equals('d'));
        }
    }

    [TestFixture]
    public class Remove
    {
        private List<int> collection;

        [SetUp]
        public void SetUp()
        {
            this.collection = new List<int>() { 1, 1, 2, 2, 3, 3 };
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_predicate_is_null()
        {
            this.collection.Remove((Func<int, bool>)null);
        }

        [Test]
        public void should_remove_all_items_that_matches_predicate()
        {
            this.collection.Remove(x => x.Equals(2));

            Assert.AreEqual(4, this.collection.Count);
            Assert.IsFalse(this.collection.Contains(2));
        }

        [Test]
        public void should_not_remove_items_that_does_not_match_predicate()
        {
            this.collection.Remove(x => x.Equals(1) || x.Equals(2));

            Assert.AreEqual(2, this.collection.Count);
            Assert.IsTrue(this.collection.Contains(3));
        }
    }

    [TestFixture]
    public class CrossProduct
    {
        [Test]
        public void should_return_a_collection_of_the_total_cross_product()
        {
            var collection = new List<int>() { 1, 2 };
            var result = collection.CrossProduct();

            Assert.AreEqual(4, result.Count());
            Assert.IsTrue(result.Contains(x => x.Item1 == 1 && x.Item2 == 1));
            Assert.IsTrue(result.Contains(x => x.Item1 == 1 && x.Item2 == 2));
            Assert.IsTrue(result.Contains(x => x.Item1 == 2 && x.Item2 == 1));
            Assert.IsTrue(result.Contains(x => x.Item1 == 2 && x.Item2 == 2));
        }
    }

    [TestFixture]
    public class dictionary_AsReadOnly
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_dictionary_is_null()
        {
            ((IDictionary<string, string>)null).AsReadOnly();
        }

        [Test]
        public void should_return_a_read_only_instance()
        {
            var dictionary = new Dictionary<string, string>() {
                { "foo", "bar" }
            };

            var readonlyDictionary = dictionary.AsReadOnly();

            Assert.AreEqual(typeof(ReadOnlyDictionary<string, string>), readonlyDictionary.GetType());
            Assert.AreEqual("bar", readonlyDictionary["foo"]);
        }


    }

    [TestFixture]
    public class collection_AsSynchronized
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_collection_is_null()
        {
            ((ICollection<string>)null).AsSynchronized();
        }

        [Test]
        public void should_return_synchronized_collection()
        {
            var c = new List<string>();
            var s = c.AsSynchronized();

            Assert.AreEqual(typeof(SynchronizedCollection<string>), s.GetType());
        }
    }

    [TestFixture]
    public class sequence_of_strings_Concatenate
    {
        string[] sequence;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            sequence = new string[] { "a", "b", "c" };
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_sequence_is_null()
        {
            Legend.Collections.Collection.Concatenate(null);
        }

        [Test]
        public void should_concatenate_values()
        {
            var result = sequence.Concatenate();
            Assert.AreEqual(result, "abc");
        }
    }

    [TestFixture]
    public class sequence_of_strings_Concatenate_with_specified_separator
    {
        string[] sequence;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            sequence = new string[] { "a", "b", "c" };
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_sequence_is_null()
        {
            Legend.Collections.Collection.Concatenate(null, ",");
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_separator_is_null()
        {
            sequence.Concatenate(null);
        }

        [Test]
        public void should_concatenate_values_separated_by_separator()
        {
            var result = sequence.Concatenate(", ");
            Assert.AreEqual(result, "a, b, c");
        }
    }

    [TestFixture]
    public class sequence_of_strings_Concatenate_with_specified_separator_prefix_and_suffix
    {
        string[] sequence;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            sequence = new string[] { "a", "b", "c" };
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_sequence_is_null()
        {
            Legend.Collections.Collection.Concatenate(null, ",", string.Empty, string.Empty);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_separator_is_null()
        {
            sequence.Concatenate(null, string.Empty, string.Empty);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_prefix_is_null()
        {
            sequence.Concatenate(string.Empty, null, string.Empty);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_suffix_is_null()
        {
            sequence.Concatenate(string.Empty, string.Empty, null);
        }

        [Test]
        public void should_concatenate_string_with_separator_prefix_and_suffix_appended()
        {
            var result = sequence.Concatenate(", ", "(", ")");
            Assert.AreEqual("(a, b, c)", result);
        }
    }

    [TestFixture]
    public class Weave
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_first_collection_is_null()
        {
            IEnumerable<int> c = null;
            c.Weave(Enumerable.Empty<int>());
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_other_collection_is_null()
        {
            Enumerable.Empty<int>().Weave(null);
        }

        [Test]
        public void should_weave_equally_long_collections_starting_with_the_first()
        {
            var first = new string[] { "a", "c" };
            var second = new string[] { "b", "d" };

            var result = first.Weave(second);

            Assert.IsTrue(result.SequenceEqual(new string[] { "a", "b", "c", "d" }));
        }

        [Test]
        public void should_weave_longer_first_collection_outside_of_other_collection()
        {
            var first = new string[] { "a", "c", "d" };
            var second = new string[] { "b" };

            var result = first.Weave(second);

            Assert.IsTrue(result.SequenceEqual(new string[] { "a", "b", "c", "d" }));
        }

        [Test]
        public void should_weave_shorter_first_collection_before_other_collection()
        {
            var first = new string[] { "b" };
            var second = new string[] { "a", "c", "d" };
            
            var result = first.Weave(second);

            Assert.IsTrue(result.SequenceEqual(new string[] { "b", "a", "c", "d" }));
        }

        [Test]
        public void should_call_dispose_on_enumerators()
        {
            var first = A.Fake<IEnumerable<int>>();
            var firstEnumerator = A.Fake<IEnumerator<int>>();
            first.CallsTo(x => x.GetEnumerator()).Returns(firstEnumerator);

            var second = A.Fake<IEnumerable<int>>();
            var secondEnumerator = A.Fake<IEnumerator<int>>();
            second.CallsTo(x => x.GetEnumerator()).Returns(secondEnumerator);

            first.Weave(second).ToArray();

            A.CallTo(() => firstEnumerator.Dispose()).MustHaveHappened();
            A.CallTo(() => secondEnumerator.Dispose()).MustHaveHappened();
        }

        [Test, ExpectedException(typeof(NotSupportedException))]
        public void should_throw_exception_when_reset_is_called_on_enumerator()
        {
            var first = new string[] { "b" };
            var second = new string[] { "a", "c", "d" };

            var e = first.Weave(second).GetEnumerator();
            e.Reset();
        }

        [Test]
        public void enumerator_should_return_current_value_when_not_using_generic_interface()
        {
            var first = new string[] { "b" };
            var second = new string[] { "a", "c", "d" };

            IEnumerator e = first.Weave(second).GetEnumerator();

            while (e.MoveNext())
            {
                object o = e.Current;
            }
        }

        [Test]
        public void should_return_enumerator_when_GetEnumerator_is_called_on_non_generic_interface()
        {
            var first = new string[] { "b" };
            var second = new string[] { "a", "c", "d" };

            IEnumerable e = first.Weave(second);
            Assert.IsNotNull(e.GetEnumerator());
        }
    }

    [TestFixture]
    public class dictionary_AsSynchronized
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_dictionary_is_null()
        {
            Dictionary<string, string> d = null;
            d.AsSynchronized();
        }

        [Test]
        public void should_return_synchronized_dictionary_wrapping_the_specified_dictionary()
        {
            Dictionary<string, string> d = new Dictionary<string, string>() 
            {
                { "a", "a"},
                { "b", "b"}
            };

            var sd = d.AsSynchronized();

            Assert.AreEqual("a", sd["a"]);
            Assert.AreEqual("b", sd["b"]);
        }
    }

    [TestFixture]
    public class dictionary_GetValue
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public virtual void throws_when_dictionary_is_null()
        {
            Legend.Collections.Collection.GetValue(null, 1, () => string.Empty);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public virtual void throws_when_defualtValue_is_null()
        {
            new Dictionary<int, string>().GetValue(1, null);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public virtual void throws_when_key_is_null()
        {
            new Dictionary<string, string>().GetValue(null, () => string.Empty);
        }

        [Test]
        public virtual void returns_the_value_in_the_dictionary_when_it_exists()
        {
            var dictionary = new Dictionary<int, string>() {{1, "foo"}};
            var result = dictionary.GetValue(1, () => "bar");
            Assert.AreEqual("foo", result);
        }

        [Test]
        public virtual void returns_the_value_of_the_function_when_key_does_not_exist()
        {
            var dictionary = new Dictionary<int, string>() { { 1, "foo" } };
            var result = dictionary.GetValue(2, () => "bar");
            Assert.AreEqual("bar", result);
        }

        [Test]
        public virtual void inserts_the_value_of_the_function_when_key_does_not_exist()
        {
            var dictionary = new Dictionary<int, string>() { { 1, "foo" } };
            dictionary.GetValue(2, () => "bar");

            Assert.AreEqual("bar", dictionary[2]);
        }

        
    }
}
